﻿using System;

namespace Ewk.Math.ComputationTypes
{
    /// <summary>
    /// Represents a function with one parameter.
    /// </summary>
    /// <typeparam name="T">The <see cref="Type"/> of the parameter of the function.</typeparam>
    /// <typeparam name="TResult">the <see cref="Type"/> of the result of the function.</typeparam>
    public struct Function<T, TResult>
        : IComparable, IFormattable, IConvertible, IComparable<Function<T, TResult>>, IEquatable<Function<T, TResult>>
        where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
        where TResult : struct, IComparable, IFormattable, IConvertible, IComparable<TResult>, IEquatable<TResult>
    {
        private readonly Func<Computable<T>, Computable<TResult>> _func;

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="function">The internal function of this instance.</param>
        public Function(Func<Computable<T>, Computable<TResult>> function)
        {
            _func = function;
        }

        /// <summary>
        /// Gets the internal function of this instance.
        /// </summary>
        public Func<Computable<T>, Computable<TResult>> Func
        {
            get { return _func; }
        }

        #region Equality
        /// <summary>
        /// Compares this instance to the specified <see cref="object"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </returns>
        public int CompareTo(object obj)
        {
            return obj is Function<T, TResult>
                       ? CompareTo((Function<T, TResult>) obj)
                       : 1;
        }

        /// <summary>
        /// Compares this instance to the specified <see cref="Function{T, TResult}"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Function{T, TResult}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Function{T, TResult}"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Function{T, TResult}"/>.
        /// </returns>
        public int CompareTo(Function<T, TResult> other)
        {
            return Func(Computable<T>.Factory.Zero).CompareTo(other.Func(Computable<T>.Factory.Zero));
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="object"/> represent the same value.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="object"/> represent the same value.</returns>
        public override bool Equals(object obj)
        {
            return !ReferenceEquals(null, obj) &&
                   obj is Function<T, TResult> &&
                   Equals((Function<T, TResult>) obj);
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="Function{T, TResult}"/> represent the same value.
        /// </summary>
        /// <param name="other">The <see cref="Function{T, TResult}"/> number to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="Function{T, TResult}"/> represent the same value.</returns>
        public bool Equals(Function<T, TResult> other)
        {
            return CompareTo(other) == 0;
        }

        /// <summary>
        /// Serves as a hash function.
        /// </summary>
        /// <returns>An <see cref="int"/> that represents the hash of this instance.</returns>
        public override int GetHashCode()
        {
            return Func(Computable<T>.Factory.Zero).GetHashCode();
        }

        #endregion

        #region Formatting

        /// <summary>
        /// Creates a <see cref="string"/> representation of this <see cref="Function{T, TResult}"/>.
        /// </summary>
        /// <param name="format">The format to use for the conversion.</param>
        /// <param name="formatProvider">The formatprovider to use for the conversion.</param>
        /// <returns>A string representation of this <see cref="Function{T, TResult}"/>.</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            return ((IFormattable) Func(Computable<T>.Factory.Zero)).ToString(format, formatProvider);
        }

        /// <summary>
        /// Creates a <see cref="string"/> representation of this <see cref="Function{T, TResult}"/>.
        /// </summary>
        /// <returns>A string representation of this <see cref="Function{T, TResult}"/>.</returns>
        public override string ToString()
        {
            return Func(Computable<T>.Factory.Zero).ToString();
        }

        #endregion

        #region Implementation of IConvertible

        TypeCode IConvertible.GetTypeCode()
        {
            throw new NotImplementedException();
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Static Members
        /// <summary>
        /// Adds two <see cref="Function{T, TResult}"/> instances.
        /// </summary>
        public static Function<T, TResult> Add(Function<T, TResult> a, Function<T, TResult> b)
        {
            return new Function<T, TResult>(input => a.Func(input) + b.Func(input));
        }

        /// <summary>
        /// Subtracts <see cref="Function{T, TResult}">b</see> from <see cref="Function{T, TResult}">a</see>.
        /// </summary>
        public static Function<T, TResult> Subtract(Function<T, TResult> a, Function<T, TResult> b)
        {
            return new Function<T, TResult>(input => a.Func(input) - b.Func(input));
        }

        /// <summary>
        /// Multiplies two <see cref="Function{T, TResult}"/> instances.
        /// </summary>
        public static Function<T, TResult> Multiply(Function<T, TResult> a, Function<T, TResult> b)
        {
            return new Function<T, TResult>(input => a.Func(input) * b.Func(input));
        }

        /// <summary>
        /// Divides <see cref="Function{T, TResult}">a</see> by <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static Function<T, TResult> Divide(Function<T, TResult> a, Function<T, TResult> b)
        {
            return new Function<T, TResult>(input => a.Func(input) / b.Func(input));
        }

        /// <summary>
        /// Computes the square root of the specified <see cref="Function{T, TResult}"/>.
        /// </summary>
        /// <param name="value">The <see cref="Function{T, TResult}"/> to calculate the square root from.</param>
        /// <returns>A <see cref="Function{T, TResult}"/> that represents the square root of the specified <see cref="Function{T, TResult}"/>.</returns>
        public static Function<T, TResult> Sqrt(Function<T, TResult> value)
        {
            return new Function<T, TResult>(input => value.Func(input).Sqrt());
        }

        /// <summary>
        /// Returns a specified number raised to the specified power.
        /// </summary>
        /// <param name="x">A function to be raised to a power.</param>
        /// <param name="y">A function that specifies a power.</param>
        /// <returns>The number x raised to the power y.</returns>
        public static Function<T, TResult> Pow(Function<T, TResult> x, Function<T, TResult> y)
        {
            return new Function<T, TResult>(input => x.Func(input).Pow(y.Func(input)));
        }

        /// <summary>
        /// Chains two <see cref="Function{TInner, TResult}"/> instances. For example: outerFunction°innerFunction = f°g = f(g(x))
        /// </summary>
        /// <typeparam name="TInner">The <see cref="Type"/> of <paramref name="innerFunction"/>.</typeparam>
        /// <param name="outerFunction">The function that takes the <paramref name="innerFunction"/> as parameter.</param>
        /// <param name="innerFunction">The function that is used as a parameter for the <paramref name="outerFunction"/>.</param>
        /// <returns>A function that chains <paramref name="outerFunction"/> and <paramref name="innerFunction"/>.</returns>
        public static Function<TInner, TResult> Chain<TInner>(
            Function<T, TResult> outerFunction,
            Function<TInner, T> innerFunction)
            where TInner : struct, IComparable, IFormattable, IConvertible, IComparable<TInner>, IEquatable<TInner>
        {
            return new Function<TInner, TResult>
                (
                input =>
                outerFunction.Func
                    (
                        innerFunction.Func(input)
                    )
                );
        }

        #region Overloaded operators
        /// <summary>
        /// Adds two <see cref="Function{T, TResult}"/> instances.
        /// </summary>
        public static Function<T, TResult> operator +(Function<T, TResult> a, Function<T, TResult> b)
        {
            return Add(a, b);
        }

        /// <summary>
        /// Subtracts <see cref="Function{T, TResult}">b</see> from <see cref="Function{T, TResult}">a</see>.
        /// </summary>
        public static Function<T, TResult> operator -(Function<T, TResult> a, Function<T, TResult> b)
        {
            return Subtract(a, b);
        }

        /// <summary>
        /// Multiplies two <see cref="Function{T, TResult}"/> instances.
        /// </summary>
        public static Function<T, TResult> operator *(Function<T, TResult> a, Function<T, TResult> b)
        {
            return Multiply(a, b);
        }

        /// <summary>
        /// Divides <see cref="Function{T, TResult}">a</see> by <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static Function<T, TResult> operator /(Function<T, TResult> a, Function<T, TResult> b)
        {
            return Divide(a, b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is equal to <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator ==(Function<T, TResult> a, Function<T, TResult> b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is not equal to <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator !=(Function<T, TResult> a, Function<T, TResult> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is smaller than <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator <(Function<T, TResult> a, Function<T, TResult> b)
        {
            return a.CompareTo(b) < 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is greater than <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator >(Function<T, TResult> a, Function<T, TResult> b)
        {
            return a.CompareTo(b) > 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is equal to or smaller than <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator <=(Function<T, TResult> a, Function<T, TResult> b)
        {
            return !(a > b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Function{T, TResult}">a</see> is equal to or greater than <see cref="Function{T, TResult}">b</see>.
        /// </summary>
        public static bool operator >=(Function<T, TResult> a, Function<T, TResult> b)
        {
            return !(a < b);
        }
        #endregion
        #endregion
    }
}